package com.smartboard.network.socket;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Host端的socket，运行在host进程
 * @author cheasonxie
 */
public class SocketHost {
    private static final Logger LOG = LoggerFactory.getLogger(SocketHost.class);

    private static final int MAX_QUEUE_SIZE = 10;

    private int mPort;
    private ServerSocket mServerSocket = null;
    private List<Client> mClients;
    private OnReceiveListener mRecvListener = null;
    private OnClientDisconnectListener mClientDisconnectListener;
    
    public interface OnClientDisconnectListener {
        public void onClientDisconnect(Socket socket);
    }

    public SocketHost(int port) {
        mPort = port;
        mClients = new ArrayList<>();
    }

    public boolean open() {
        if(mServerSocket != null && !mServerSocket.isClosed())
            return true;
        new Thread(new TcpServer()).start();
        return true;
    }

    public void close() {
        try {
            if (mServerSocket != null && !mServerSocket.isClosed()) {
                mServerSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean isClosed() {
        if(mServerSocket != null)
            return mServerSocket.isClosed();
        return true;
    }

    private Client getClient(Socket socket) {
        for(Client c : mClients) {
            if(c.mSocket == socket) {
                return c;
            }
        }
        return null;
    }

    public void send(Socket socket, Object data) {
        try {
            Client c = getClient(socket);
            if(c != null)
                c.mSendDataQueue.put(data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void sendToAll(Object data) {
        for(Client c : mClients) {
            try {
                c.mSendDataQueue.put(data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void removeClient(Client client) {
        LOG.debug("client " + client.mSocket.getLocalAddress() + " disconnect!!");
        for(Client c : mClients) {
            if(c == client) {
                mClients.remove(c);
                return;
            }
        }
    }

    public void setOnReceiveListener(OnReceiveListener listener) {
        mRecvListener = listener;
    }
    
    public void setOnClientDisconnectListener(OnClientDisconnectListener listener) {
        mClientDisconnectListener = listener;
    }

    private class TcpServer implements Runnable {
        @Override
        public void run() {
            mServerSocket = null;
            try {
                mServerSocket = new ServerSocket(mPort);
            } catch (IOException e) {
                LOG.error("fail to create connection:" + mPort);
                e.printStackTrace();
                return;
            }

            while (true) {
                if(mServerSocket.isClosed())
                    break;
                try {
                    LOG.debug("wait for client socket connect");
                    Socket socket = mServerSocket.accept();
                    Client client = new Client(socket);
                    mClients.add(client);
                    LOG.debug("client connected! total: " + mClients.size());
                    client.startReadThread();
                    client.startWriteThread();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class Client {
        Socket mSocket;
        BlockingQueue<Object> mSendDataQueue;
        public Client(Socket socket) {
            mSocket = socket;
            mSendDataQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE);
        }
        
        private void handleSocketError() {
            try {
                mSocket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            removeClient(this);
            if(mClientDisconnectListener != null) {
                mClientDisconnectListener.onClientDisconnect(mSocket);
            }
        }

        // 读数据线程(一个client对应一个线程）
        private void startReadThread() {
            new Thread() {
                @Override
                public void run() {
                    ObjectInputStream in = null;
                    try {
                        in = new ObjectInputStream(mSocket.getInputStream());
                        while(!isClosed()) {
                            Object data = in.readObject();
                            if(data != null) {
                                LOG.debug("client: " + mSocket.getInetAddress() + "receive object:" + data);
                                if(mRecvListener != null)
                                    mRecvListener.onReceive(mSocket, data);
                            } else {
                                break;
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        handleSocketError();
                        return;
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        handleSocketError();
                        return;
                    } finally {
                        if(in != null) {
                            try {
                                in.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }.start();
        }

        // 发送数据线程(一个client对应一个线程）
        private void startWriteThread(){
            new Thread() {
                @Override
                public void run() {
                    ObjectOutputStream out = null;
                    try {
                        out = new ObjectOutputStream(mSocket.getOutputStream());
                        while(!isClosed()) {
                            Object data = mSendDataQueue.take();
                            out.writeUnshared(data);
                            //LOG.debug("send object:" + data);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        handleSocketError();
                        return;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        handleSocketError();
                        return;
                    } finally {
                        if(out != null) {
                            try {
                                out.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }.start();
        }
    }
}
